using System;
using System.Data;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

namespace Mani.Common {

    public class StateManagerBase {
        private State currentState;
        public State CurrentState { get { return currentState; } }
        public OnTransitionCompleted completedMethod;

        public List<State> states = new List<State>(); // TODO: don't expose this!

        protected ReadOnlyCollection<State> States {
            get { return states.AsReadOnly(); }
        }

        public State this[int stateId] {
            get {
                foreach ( State state in States )
                    if ( state.Id == stateId )
                        return state;
                throw new StateManagerException("State not found.");
            }
        }

        // 
        public State CreateState(int id, bool final, OnStateEnter enter) {
            State st = new State(id, final, enter);
            states.Add(st);
            return st;
        }

        public State CreateState(int id, bool final) {
            State st = new State(id, final, null);
            states.Add(st);
            return st;
        }
        //

        public void SetStartingState( State init ) {
            if ( currentState != null )
                throw new Exception( "StateManager has already been started!" );
            currentState = init;

            if ( currentState.enterMethod != null )
                try {
                    // Removed - I think this should not be called for startingstates, because it means 
                    // we are just loading something which is already in this state. - Hope nothing breaks.
                    //currentState.enterMethod(); 
                }
                catch ( Exception e ) {
                    throw new TransitionException( TransitionException.Type.OnInitialStateEnterAborted, e );
                }
        }

        public void SetStartingState( int stateid ) {
            if ( currentState != null )
                throw new Exception( "StateManager has already been started!" );
            foreach ( State st in States ) {
                if ( st.Id == stateid ) {
                    SetStartingState( st );
                    return;
                }
            }
            throw new Exception( "StateManager, initialized to unknown state: " + stateid );
        }

        public void Send( int eventId ) {
            currentState = currentState.DoEvent( eventId );
            completedMethod();
            if ( currentState.enterMethod != null )
                try {
                    currentState.enterMethod();
                }
                catch ( Exception e ) {
                    throw new TransitionException( TransitionException.Type.OnStateEnterAborted, e );
                }

        }

        public Dictionary<int,Transition> GetTransitionsByState(int stateId)
        {
            State state = this.states.SingleOrDefault<State>(s => s.Id == stateId);

            if ( state == null )
                return null;

            return state.Transitions;
        }
    }

    public delegate void OnStateEnter();
    public delegate bool TransitionGuard( out string error );
    public delegate void TransitionAction();
    public delegate void OnTransitionCompleted();

    
    public class State {
        internal OnStateEnter enterMethod;
        private bool isFinal;
        private int id;
        public bool IsFinal { get { return isFinal; } }
        public int Id { get { return id; } }
        public OnStateEnter EnterEvent { get { return enterMethod; } }
        public Dictionary<int, Transition> Transitions = new Dictionary<int,Transition>();

        public void AddNewTransition( int eventId, string name, State nextState, TransitionGuard guard, TransitionAction action ) {
            // Note (Hadi): I added the name field so that it an be used for the event names in the UIs...
            //              If it's not used, a dummy "--" can be simply passed.
            Transition t = new Transition(name, nextState, guard, action);
            Transitions.Add( eventId, t );
        }

        internal State( int id, bool final, OnStateEnter methods1) {
            enterMethod = methods1;
            this.id = id;
            isFinal = final;
        }


        public Transition this[int eventId] {
            get {
                Transition transition;
                if (Transitions.TryGetValue(eventId, out transition))
                    return transition;
                else
                    throw new StateManagerException("Transition not found.");
            }
        }

        public State DoEvent( int eventid ) {
            if (isFinal) {
                throw new TransitionException(TransitionException.Type.InvalidEventOnFinalState);
            }

            Transition t;
            if ( Transitions.TryGetValue( eventid, out t ) ) {
                if ( t.Guard != null ) {
                    string err;
                    bool result;
                    
                    foreach ( Delegate delg in t.Guard.GetInvocationList() ) {
                        try {
                            TransitionGuard g = (TransitionGuard)delg;
                            result = g(out err );
                        }
                        catch ( Exception e ) {
                            throw new TransitionException( TransitionException.Type.GuardAborted, e );
                        }
                        if (!result) {
                            throw new TransitionException(TransitionException.Type.GuardFailed, err);
                        }
                    }
                }

                if (t.Action != null) {
                    try {
                        t.Action();
                    }
                    catch (Exception e) {
                        throw new TransitionException(TransitionException.Type.ActionAborted, e);
                    }
                }

                return t.NextState;
            }

            throw new TransitionException( TransitionException.Type.NoTransitionForEvent );
        }
    }


    public class Transition {
        private TransitionGuard guard;
        private TransitionAction action;
        private State nextState;
        private string name;
        public string Name { get { return name; } }
        public TransitionGuard Guard { get { return guard; } }
        public TransitionAction Action { get { return action; } }
        public State NextState { get { return nextState; } }


        public Transition( string name, State next, TransitionGuard guard, TransitionAction action ) {
            this.name = name;
            this.guard = guard;
            this.action = action;
            this.nextState = next;
        }

        public void AddTransitionAction(TransitionAction action) {
            this.action += action;
        }
    }


    public class TransitionException : Exception {

        public enum Type {
            GuardFailed,
            GuardAborted,
            ActionAborted,
            OnStateExitAborted,
            OnInitialStateEnterAborted,
            OnStateEnterAborted,
            NoTransitionForEvent,
            InvalidEventOnFinalState
        }

        private Type failure;
        public Type Failure { get { return failure; } }
        private string error;
        public string Error { get { return error; } }

        public TransitionException( Type failure )
            : base( failure.ToString() ) {
            this.failure = failure;
        }

        public TransitionException( Type failure, string error )
            : base( failure.ToString() + " : " + error ) {
            this.error = error;
            this.failure = failure;
        }

        public TransitionException( Type failure, Exception inner )
            : base( failure.ToString(), inner ) {
            this.failure = failure;
        }
    }


    public class StateManagerException : Exception {
        public StateManagerException( string message ) : base( message ) { }
        public StateManagerException( string message, Exception inner ) : base( message, inner ) { }
    }
}


